﻿using System.Reactive;
using System.Reactive.Linq;
using AntDesign;
using Blazor.CoffeeTalesApi.Dto;
using Blazor.CoffeeTalesApi.Entity;
using Blazor.Localization;
using Blazor.Pages.UpdateSetting.Modals.ChangeNativeVersion;
using Blazor.Services.Device;
using Blazor.Services.Update;
using DynamicData;
using Microsoft.Extensions.Localization;
using ReactiveUI;

namespace Blazor.ViewModels.UpdateSetting;

public class UpdateSettingViewModel : ReactiveObject
{
    private readonly IUpdateService _updateService;
    private readonly IStringLocalizer<UpdateSettingLocalization> _loc;
    private readonly IDeviceService _deviceService;
    private readonly ILogger _logger;
    private readonly ModalService _modalService;

    private uint _latestNativeVersion;
    private uint _minNativeVersion;
    private string _jumpUrl = string.Empty;
    private Channel? _currentChannel;
    private SettingsViewModel? _currentSettings;
    private readonly SourceCache<ChannelInfo, Channel> _channelInfoCache = new(p => p.Name);


    #region OAPH

    private readonly ObservableAsPropertyHelper<bool> _isLoadingChannelInfo;

    private readonly ObservableAsPropertyHelper<bool> _isChangingNativeVersion;

    private readonly ObservableAsPropertyHelper<bool> _isChangingJumpUrl;

    #endregion

    #region Public Properties

    public uint LatestNativeVersion
    {
        get => _latestNativeVersion;
        private set => this.RaiseAndSetIfChanged(ref _latestNativeVersion, value);
    }

    public uint MinNativeVersion
    {
        get => _minNativeVersion;
        private set => this.RaiseAndSetIfChanged(ref _minNativeVersion, value);
    }

    public string JumpUrl
    {
        get => _jumpUrl;
        private set => this.RaiseAndSetIfChanged(ref _jumpUrl, value);
    }

    public Channel? CurrentChannel
    {
        get => _currentChannel;
        private set => this.RaiseAndSetIfChanged(ref _currentChannel, value);
    }

    public SettingsViewModel? CurrentSettings
    {
        get => _currentSettings;
        private set => this.RaiseAndSetIfChanged(ref _currentSettings, value);
    }


    public bool IsLoadingChannelInfo => _isLoadingChannelInfo.Value;


    public bool IsChangingJumpUrl => _isChangingJumpUrl.Value;

    public bool IsChangingNativeVersion => _isChangingNativeVersion.Value;

    #endregion

    #region Commands

    public ReactiveCommand<Channel?, Unit> SelectChannel { get; }

    public ReactiveCommand<Unit, Unit> Refresh { get; }

    public ReactiveCommand<ChangeNativeVersionOptions.EditType, Unit> ChangeNativeVersion { get; }

    public ReactiveCommand<Unit, Unit> ChangeJumpUrl { get; }

    #endregion

    #region Interactions

    public Interaction<ChangeNativeVersionOptions, ChangeNativeVersionResult?> ChangeNativeVersionInteraction { get; } =
        new();

    public Interaction<string, Uri?> ChangeJumpUrlInteraction { get; } = new();

    #endregion


    public UpdateSettingViewModel(IUpdateService updateService, ILogger<UpdateSettingViewModel> logger, ModalService modalService, IStringLocalizer<UpdateSettingLocalization> loc, IDeviceService deviceService)
    {
        _updateService = updateService;
        _logger = logger;
        _modalService = modalService;
        _loc = loc;
        _deviceService = deviceService;
        _currentSettings = null;

        SelectChannel = ReactiveCommand.CreateFromTask<Channel?>(SelectChannelImpl, Observable.Return(true));

        var currentChannelNotNull = this.WhenAnyValue(x => x.CurrentChannel)
            .Select(c => c is not null)
            .Publish()
            .RefCount();

        Refresh = ReactiveCommand.CreateFromTask(RefreshImpl, currentChannelNotNull);
        Refresh.IsExecuting.ToProperty(this, x => x.IsLoadingChannelInfo, out _isLoadingChannelInfo);

        ChangeNativeVersion =
            ReactiveCommand.CreateFromTask<ChangeNativeVersionOptions.EditType>(ChangeNativeVersionImpl,
                currentChannelNotNull);
        ChangeNativeVersion.IsExecuting.ToProperty(this, x => x.IsChangingNativeVersion, out _isChangingNativeVersion);

        ChangeJumpUrl = ReactiveCommand.CreateFromTask(ChangeJumpUrlImpl, currentChannelNotNull);
        ChangeJumpUrl.IsExecuting.ToProperty(this, x => x.IsChangingJumpUrl, out _isChangingJumpUrl);

        _channelInfoCache.Connect()
            .SelectMany(changes => changes)
            .Where(change => change.Key == CurrentChannel)
            .Select(c => (Channel?)c.Key)
            .Merge(this.WhenAnyValue(x => x.CurrentChannel))
            .Select(c =>
            {
                if (c is null)
                {
                    return null;
                }

                var optional = _channelInfoCache.Lookup(c.Value);
                return optional.HasValue ? optional.Value : null;
            })
            .DistinctUntilChanged()
            .Subscribe(OnCurrentChannelChanged);
    }

    private void OnCurrentChannelChanged(ChannelInfo? channelInfo)
    {
        if (channelInfo is not null)
        {
            if (CurrentChannel is null)
            {
                throw new NullReferenceException("CurrentChannel is null");
            }

            LatestNativeVersion = channelInfo.LatestNativeVersion;
            MinNativeVersion = channelInfo.MinNativeVersion;
            JumpUrl = channelInfo.JumpUrl;

            _currentSettings = new SettingsViewModel(channelInfo.HotUpdateSettings, _updateService, _loc, _deviceService, _logger,
                CurrentChannel.Value, _modalService);
        }
        else
        {
            LatestNativeVersion = 0;
            MinNativeVersion = 0;
            JumpUrl = string.Empty;

            _currentSettings = null;
        }
    }

    #region Command Implements

    private async Task SelectChannelImpl(Channel? c)
    {
        CurrentChannel = c;
        
        if (c is null)
        {
            return;
        }

        if (_channelInfoCache.Lookup(c.Value).HasValue)
        {
            return;
        }
        
        var channelInfo = await _updateService.FetchChannelInfoAsync(c.Value) ??
                          throw new Exception("Failed to fetch channel info");

        _channelInfoCache.AddOrUpdate(channelInfo);
    }

    private async Task RefreshImpl()
    {
        if (CurrentChannel is null)
        {
            throw new InvalidOperationException("CurrentChannel is null");
        }

        var channelInfo = await _updateService.FetchChannelInfoAsync(CurrentChannel.Value) ??
                          throw new Exception("Failed to fetch channel info");

        _channelInfoCache.AddOrUpdate(channelInfo);
    }

    private async Task ChangeNativeVersionImpl(ChangeNativeVersionOptions.EditType editType)
    {
        if (CurrentChannel is null)
        {
            throw new InvalidOperationException("CurrentChannel is null");
        }

        var result = await ChangeNativeVersionInteraction.Handle(new ChangeNativeVersionOptions()
        {
            LatestNativeVersion = LatestNativeVersion,
            MinNativeVersion = MinNativeVersion,
            Type = editType,
        });

        if (result is null)
        {
            return;
        }

        var latest = result.Value.Latest;
        var min = result.Value.Min;

        var success = await _updateService.SetNativeVersionAsync(CurrentChannel.Value, latest, min);

        if (!success)
        {
            throw new Exception("Failed to set native version");
        }

        LatestNativeVersion = latest;
        MinNativeVersion = min;
    }

    private async Task ChangeJumpUrlImpl()
    {
        if (CurrentChannel is null)
        {
            throw new InvalidOperationException("CurrentChannel is null");
        }

        var result = await ChangeJumpUrlInteraction.Handle(JumpUrl);

        if (result is null)
        {
            return;
        }

        var success = await _updateService.SetJumpUrlAsync(CurrentChannel.Value, result);

        if (!success)
        {
            throw new Exception("Failed to set jump url");
        }

        JumpUrl = result.ToString();
    }

    #endregion
}